/*
 * GAOHUA BPC测试程序
 * 详见随带excel解析文件
 * SUMMARY:
 * 对ARDUINO适配高华BPC对时模块
 * Author:Yizhou Wang VER:0.1.BETA MAY08,2021
 * 存在已知的BUG:
 * 年份计算存在问题。
 * 没有累加校验机制；待修复年份计算问题后加入。
 * 
 * VER1.0 PUBLISH
 * 本版本为正式版，已修复了日期显示BUG，并增加了上电初始化机制。
 * 接线：
 * 在11脚接一个LED，接阳极，作为状态灯显示（灯处于呼吸状态时为休眠，若PON为高电平时会被唤醒进入待收模式）。
 * BPC模块PON脚接ARDUINO的4脚。
 * BPC模块DAT脚接6脚（数据脚）。
 * BPC模块CLK脚接5脚（数据脚)。
 * BPC模块RST脚接3脚（复位脚）。
 * 若上电初始化成功，则会在串口打印一个Standby...。
 * 若接收到BPC模块传到的数据，会显示解码后的信息和数据原文。
 * 
 * TRUST LEVEL解析
   若TRUST LEVEL为0，则说明这帧数据时间是过期无效的（既BPC模块RESET后就没有收到有效的BPC数据）。
   若TRUST LEVEL为1，说明这帧数据时间是可以参照的，因为该模块RESET后收到有效的BPC数据，但是已经超过了24小时，可以保证一定的计时精度。
   若TRUST LEVEL为2，说明这帧数据时间是可以参照的，该模块在24小时内收到有效的BPC数据，精度较为良好。
   若TRUST LEVEL为3，说明这帧数据时间是非常适合参照的，因为该模块刚刚收到有效的BPC数据。

*VER 2.1 SUMMERY MAY10.2021
*本版本增加了对彩色1602显示的支持，并且支持串口和1602多设备的调试
*引脚:1602 D7接A5脚，D6接A4脚，D5接A3脚，D4接A2脚，EN接A1脚，RS接A0脚
*若您的1602屏幕有RGB背光显示功能，可以按照以下引脚定义连接
*红背光LED，连接10脚
*绿背光LED,连接9脚
*蓝背光LED,连接3脚
*若收到的数据无效，则会在屏幕上显示COL FAIL!并且显示红背景灯
*若正在收波，则屏幕会显示SEARCHING...字样，背景灯变蓝
*若收到有效的数据，则屏幕会显示收到的时间信息，背景灯变绿
*/
#include<LiquidCrystal.h>
int RS=A0;
int EN=A1;
int D4=A2;
int D5=A3;
int D6=A4;
int D7=A5;
LiquidCrystal lcd(RS,EN,D4,D5,D6,D7);

#define LedR 10
#define LedG 9
#define LedB 3//1602背光控制引脚
#define PON 4
#define CLK 5
#define DAT 6
#define STA 11
#define RST 2
char RecFromBpcModule[50];//原始数据，高位在前，低位在后

int YEAR;//年份，存放结果
int MONTH_RESULT;//月份，存放结果
int VALI;//数据有效性
int DATE;//日期，存放结果
int WEEK_RESULT;//星期，存放结果
int HOUR_RESULT;//时，存放结果
int MINUTE;//分，存放结果
int SECOND;//秒，存放结果
int ReqTime;//接收请求发送次数

int IsDataCorrupted;
int NeedOutput;


void setup() {
  
  Serial.begin(9600); 
  pinMode(PON,INPUT);//当PON高电平时，代表模块进入接收状态
  pinMode(CLK,INPUT);//接收时钟信号
  pinMode(DAT,INPUT);//接收数据信号  
  pinMode(EN,OUTPUT);//1602液晶管脚初始化  
  pinMode(RS,OUTPUT);
  pinMode(D4,OUTPUT);
  pinMode(D5,OUTPUT);
  pinMode(D6,OUTPUT);
  pinMode(D7,OUTPUT); 
  pinMode(LedR,OUTPUT);//1602背光，红色通道，设定模式OUTPUT
  pinMode(LedG,OUTPUT);//1602背光，绿色通道，设定模式OUTPUT
  pinMode(LedB,OUTPUT);//1602背光，蓝色通道，设定模式OUTPUT
  lcd.begin(16, 2);
  lcd.clear();
  lcd.setCursor(0, 0);//(X,Y坐标)
  lcd.print("Ready...");
  Lcd1602BacklightControl(255,255,0);
  
  pinMode(STA,OUTPUT);//BPC模块运行状态信号
  digitalWrite(STA,LOW);//状态灯
  pinMode(RST,OUTPUT);//复位运行状态信号
  digitalWrite(RST,HIGH);
  delay(500);
  digitalWrite(RST,LOW);  
  delay(1000);
  Serial.println("Standby...");

}
  
char Lcd1602BacklightControl(int clrr,int clrg,int clrb)//控制1602背光颜色的函数，分别输入RGB每个颜色的成分，范围从0-255
{
  analogWrite(LedR,clrr);
  analogWrite(LedG,clrg);
  analogWrite(LedB,clrb);
  }
  
void TranscodeRec()//将收到的原始数据分割并计算，将结果存入公用变量
{
 int YEAR_TENS;
 int YEAR_UNIT;
 int MONTH;//月份
 int STATUS;//状态码
 int DATE_TENS;//日期十位数
 int DATE_UNIT;//日期个位数
 int AM_PM;//上午下午
 int WEEK;//星期
 int HOUR;//时
 int MINUTE_TENS;//分（十位）
 int MINUTE_UNIT;//分（个位）
 int SECOND_TENS;//秒（十位）
 int SECOND_UNIT;//秒（个位）
 int CHECK_SUM;//校验累加和
 YEAR_TENS=RecFromBpcModule[1]*8+RecFromBpcModule[2]*4+RecFromBpcModule[3]*2+RecFromBpcModule[4];//范围0-9
 YEAR_UNIT=RecFromBpcModule[5]*8+RecFromBpcModule[6]*4+RecFromBpcModule[7]*2+RecFromBpcModule[8];//范围0-9；既显示范围为2000-2099，超出有可能会发生千年虫问题
 MONTH=RecFromBpcModule[9]*8+RecFromBpcModule[10]*4+RecFromBpcModule[11]*2+RecFromBpcModule[12];//范围0-12；其中0值不合法
 STATUS=RecFromBpcModule[13]*2+RecFromBpcModule[14]*1;
 /*
 若累加等于0，则说明这帧数据时间是过期无效的（既BPC模块RESET后就没有收到有效的BPC数据）
 若累加等于1，说明这帧数据时间是可以参照的，因为该模块RESET后收到有效的BPC数据，但是已经超过了24小时，可以保证一定的计时精度
 若累加等于2，说明这帧数据时间是可以参照的，该模块在24小时内收到有效的BPC数据，精度较为良好
 若累加等于3，说明这帧数据时间是非常适合参照的，因为该模块刚刚收到有效的BPC数据
 */
 DATE_TENS=RecFromBpcModule[15]*2+RecFromBpcModule[16]*1;//范围0-3
 DATE_UNIT=RecFromBpcModule[17]*8+RecFromBpcModule[18]*4+RecFromBpcModule[19]*2+RecFromBpcModule[20];//范围0-9，既表示日期可以从0-39表示，其中合法范围为1-31
 AM_PM=RecFromBpcModule[21];//范围0-1，其中AM为0，PM为1
 WEEK=RecFromBpcModule[22]*4+RecFromBpcModule[23]*2+RecFromBpcModule[24];//范围从0-8，但是合法范围为0-6，分别表示SUN-SAT
 HOUR=RecFromBpcModule[25]*8+RecFromBpcModule[26]*4+RecFromBpcModule[27]*2+RecFromBpcModule[28];//范围从0-16，合法为0-12，表示小时，配合AM_PM位判断
 MINUTE_TENS=RecFromBpcModule[29]*8+RecFromBpcModule[30]*4+RecFromBpcModule[31]*2+RecFromBpcModule[32];//范围0-5，表分钟十位
 MINUTE_UNIT=RecFromBpcModule[33]*8+RecFromBpcModule[34]*4+RecFromBpcModule[35]*2+RecFromBpcModule[36];//范围0-9，表分钟个位，分钟最大表示范围0-59
 SECOND_TENS=RecFromBpcModule[37]*8+RecFromBpcModule[38]*4+RecFromBpcModule[39]*2+RecFromBpcModule[40];//范围0-5，表秒十位
 SECOND_UNIT=RecFromBpcModule[41]*8+RecFromBpcModule[42]*4+RecFromBpcModule[43]*2+RecFromBpcModule[44];//范围0-9，表秒个位，秒最大表示范围0-59
 CHECK_SUM=RecFromBpcModule[45]*8+RecFromBpcModule[46]*4+RecFromBpcModule[47]*2+RecFromBpcModule[48];//校验和，前面44位相加并对16取模的结果，范围0-16


 YEAR=YEAR_TENS*10+YEAR_UNIT;
 MONTH_RESULT=MONTH;
 VALI=STATUS;
 DATE=DATE_TENS*10+DATE_UNIT;
 WEEK_RESULT=WEEK;
 HOUR_RESULT=AM_PM*12+HOUR;
 MINUTE=MINUTE_TENS*10+MINUTE_UNIT;
 SECOND=SECOND_TENS*10+SECOND_UNIT;//对分割好的数据进行计算，并赋值
 int sum;
 for(int i=1;i<25;i++)//检查数据完整性,对前面44个数据进行相加对16取模,如果数据正确，IsDataCorrupted=1，否则为0
  {    
    sum=RecFromBpcModule[i]+RecFromBpcModule[i+1];
    }
    if(sum%16==CHECK_SUM)
    {
      IsDataCorrupted=1;
      }
      else
      {
        IsDataCorrupted=0;
        }
  }

 
void ReceiveCode()//收到数据，虚拟595串口间隔1us检测电平信号
{
//缓存数据，高位在前，低位在后
 for(int i=1;i<49;i++)//循环48次，原始数据存储于RecFromBpcModule[int position]中
 {
  while(digitalRead(CLK)==HIGH)//时钟为高电平时
  {
    delayMicroseconds(1);//延迟1us，直到检测到低电平
    }
    while(digitalRead(CLK)==LOW)
    {
      delay(5);//等待模块将data电平拉低，延迟5ms后采样
      if(digitalRead(DAT)==HIGH)//若DAT为高电平，则该位置1
      {
        RecFromBpcModule[i]=1;
        }
        else
        {
          RecFromBpcModule[i]=0;//否则置0
          }
          while(digitalRead(CLK)==LOW)
          {
            delayMicroseconds(1);//直到时钟拉高电平进入下一位数据传输否则不退出延迟循环
            }
      }
  }
  }
void LeftShiftData(int para)//对数组左移para位
{
  for(int i=1;i<49;i++)
  {
    RecFromBpcModule[i]=RecFromBpcModule[i+para];
    }
  }
void DisplayResult()
{ 
  ReqTime++;
  Serial.print("Current time:");
  Serial.print(2000+YEAR);

  Serial.print(",");
  switch(MONTH_RESULT)
  {
  
    case 1:
    {
      Serial.print("JAN.");
      break;
      }
      case 2:
    {
      Serial.print("FEB.");
      break;
      }
      case 3:
    {
      Serial.print("MAR.");
      break;
      }
      case 4:
    {
      Serial.print("APR.");
      break;
      }
      case 5:
    {
      Serial.print("MAY.");
      break;
      }
      case 6:
    {
      Serial.print("JUN.");
      break;
      }
      case 7:
    {
      Serial.print("JUL.");
      break;
      }
      case 8:
    {
      Serial.print("AUG.");
      break;
      }
      case 9:
    {
      Serial.print("SEPT.");
      break;
      }
      case 10:
    {
      Serial.print("OCT.");
      break;
      }
      case 11:
    {
      Serial.print("NOV.");
      break;
      }
      case 12:
    {
      Serial.print("DEC.");
      break;
      }
      default:
      {
        Serial.print("ERR.MONTH");
        break;
        }
    }
  Serial.print(DATE);
  Serial.print(" ");
  switch(WEEK_RESULT)
  {
  case 0:
  {
  Serial.write("SUN");
  break;
  }
  case 1:
  {
  Serial.write("MON");
  break;
  }
  case 2:
  {
  Serial.write("TUE");
  break;
  }
  case 3:
  {
  Serial.write("WED");
  break;
  }
  case 4:
  {
  Serial.write("THU");
  break;
  }
  case 5:
  {
  Serial.write("FRI");
  break;
  }
  case 6:
  {
  Serial.write("SAT");
  break;
  }
  default:
  {
    Serial.write("ERR.WEEK");
    break;
    }
  }
  Serial.write(" ");
  Serial.print(HOUR_RESULT);
  Serial.write(":");
  Serial.print(MINUTE);
  Serial.write(":");
  Serial.print(SECOND);
  Serial.println(" ");
  Serial.write("TRUST LEVEL:");
  Serial.print(VALI);
  switch(VALI)
  {
    case 0:
    {
    Serial.print("(WORST)");
    break;
    }
    case 1:
    {
    Serial.print("(GOOD)");
    break;
    }
    case 2:
    {
    Serial.print("(FINE)");
    break;
    }
    case 3:
    {
    Serial.print("(BEST)");
    break;
    }
    }
  Serial.write("/");
  for(int i=1;i<49;i++)
  {
  Serial.write(0x30+RecFromBpcModule[i]);
  if((i)%4==0)
  {
    Serial.write(" ");//分隔符，每隔4位打印一个空格
    }
  }
  Serial.println("");//打印一个回车符
  Serial.print("REQUEST TIME:");
  Serial.print(ReqTime);
  Serial.println("");
  }
void DisplayResultBy1602()
{
  if(VALI==0)
  {
    lcd.clear();
    Lcd1602BacklightControl(255,0,0);//LCD1602背景灯变红，表示尚未收到有效数据帧
    lcd.setCursor(0, 0);
    lcd.print("COL FAIL!");
    lcd.setCursor(0,1);
    lcd.print("REQ TIME=");//显示BPC模块尝试收波的次数
    lcd.print(ReqTime);
  }
    else
    {
        lcd.clear();
        Lcd1602BacklightControl(0,255,0);      
        lcd.setCursor(0,0);
        lcd.print(YEAR);
        lcd.print("/");
        lcd.print(MONTH_RESULT);
        lcd.print("/");
        lcd.print(DATE);
        lcd.print(" ");
        lcd.print(WEEK_RESULT);

        lcd.setCursor(0,1);
        lcd.print(HOUR_RESULT);
        lcd.print(":");
        lcd.print(MINUTE);
        lcd.print(":");
        lcd.print(SECOND);    
      }    
    }  
void loop() {
if(digitalRead(PON)==LOW)
{
  analogWrite(STA,0);
  ReceiveCode();
  LeftShiftData(1);
  TranscodeRec();
  DisplayResult();
  DisplayResultBy1602();
  while(digitalRead(PON)==LOW)//待机灯，当PON处于高电平时跳出进入接收状态
  {
    for(int i=0;i<255;i++)
    {
      delay(10);
      analogWrite(STA,i);
      }
      for(int i=255;i>0;i--)
      {
        delay(10);
        analogWrite(STA,i);
        }
        
        if(Serial.available()>0)//串口输入任意值后重置BPC模块
        {
          while(Serial.available()>0)
        {
          Serial.read();
        }
        
          digitalWrite(RST,HIGH);
          delay(500);
          digitalWrite(RST,LOW);
          }
    }
  }

else
{
analogWrite(STA,500);//当PON高电平时该状态灯常亮
lcd.setCursor(0,0);
Lcd1602BacklightControl(0,0,255);
lcd.print("SEARCHING...");
}
}
